#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/wait.h>
#include <dirent.h>
#include <pwd.h>
#include <grp.h>
#include <linux/limits.h>
#include <time.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>

#define OPTIONS_SIZE		8

static char options[OPTIONS_SIZE] = { 0 };
static const char* target_dir = "../test_dir";

static inline void err_fatal(const char* msg) {
	perror(msg);
	exit(-1);
}

static int is_member(char ch) {
	for (int i = 0; i < OPTIONS_SIZE; i++) {
		if (options[i] == ch) {
			return 1;
		}
	}
	return 0;
}

/************************************* 命令选项 *************************************/

static inline int opt_hidden() {
	return is_member('a');
}

static inline int opt_long() {
	return is_member('l');
}

static inline int opt_follow() {
	return is_member('f');
}

static inline int opt_recursive() {
    return is_member('r');
}

static void cp_file(const char* src) {
    pid_t pid = fork();
    if (pid < 0) {
        err_fatal("fork");
    }
    if (pid > 0) {
        waitpid(pid, NULL, 0);
        return;
    }

    execl("../mycp", "mycp", src, target_dir, NULL);
}

static void show_entry(const char* fullpath, const char* filename, const struct stat* sb) {
    if (!S_ISDIR(sb->st_mode)) {
        cp_file(fullpath);
    }
	if (opt_long()) {
		char filetype;
		char perm[10];

		// 获取文件类型
		switch (sb->st_mode & S_IFMT) {
		case S_IFREG:
			filetype = '-';
			break;
		case S_IFDIR:
			filetype = 'd';
			break;
		case S_IFBLK:
			filetype = 'b';
			break;
		case S_IFCHR:
			filetype = 'c';
			break;
		case S_IFLNK:
			filetype = 'l';
			break;
		case S_IFIFO:
			filetype = 'p';
			break;
		case S_IFSOCK:
			filetype = 's';
			break;
		}

		// 获取文件访问权限
		for (int i = 0; i < 9; i++) {
			perm[i] = '-';
		}
		perm[9] = '\0';

		if (sb->st_mode & S_IRUSR) {
			perm[0] = 'r';
		}
		if (sb->st_mode & S_IWUSR) {
			perm[1] = 'w';
		}
		if (sb->st_mode & S_IXUSR) {
			perm[2] = 'x';
		}
		if (sb->st_mode & S_IRGRP) {
			perm[3] = 'r';
		}
		if (sb->st_mode & S_IWGRP) {
			perm[4] = 'w';
		}
		if (sb->st_mode & S_IXGRP) {
			perm[5] = 'x';
		}
		if (sb->st_mode & S_IROTH) {
			perm[6] = 'r';
		}
		if (sb->st_mode & S_IWOTH) {
			perm[7] = 'w';
		}
		if (sb->st_mode & S_IXOTH) {
			perm[8] = 'x';
		}

		// 获取文件最后修改时间
		struct tm* modtime = localtime(&sb->st_mtim.tv_sec);
		if (!modtime) {
			fprintf(stderr, "%s\n", "localtime");
			exit(-1);
		}

		char timebuf[16];
		strftime(timebuf, sizeof(timebuf), "%b %d %R", modtime);

		// 获取文件属主名
		const char* username = NULL;
		errno = 0;
		struct passwd* pwd = getpwuid(sb->st_uid);
		if (!pwd) {
			if (errno != 0) {
				err_fatal("get user name");
			}
			
			username = "?";
		}
		else {
			username = pwd->pw_name;
		}

		// 获取文件属组名
		const char* groupname = NULL;
		errno = 0;
		struct group* grp = getgrgid(sb->st_gid);
		if (!grp) {
			if (errno != 0) {
				err_fatal("get group name");
			}
			
			groupname = "?";
		}
		else {
			groupname = grp->gr_name;
		}


		printf("%c %s %4lu %s %s %10lu %s %s\n", filetype, perm, sb->st_nlink, username, 
												 groupname, sb->st_size, timebuf, filename);
	}
	else {
		if (S_ISDIR(sb->st_mode)) {
			printf("%s/ ", filename);
		}
		else {
			printf("%s ", filename);
		}
	}
}

static void myls(const char* path) {
	int ret, pathlen = strlen(path);
	char fullpath[PATH_MAX];
	struct stat sb;

	strcpy(fullpath, path);

	if (opt_follow()) {
		ret = stat(fullpath, &sb);
	}
	else {
		ret = lstat(fullpath, &sb);
	}
	if (ret == -1) {
		err_fatal(fullpath);
	}

	if (S_ISDIR(sb.st_mode)) {
        if (opt_recursive()) {
        	printf("%s: \n", fullpath);
        }
 
		if (fullpath[pathlen - 1] != '/') {
			fullpath[pathlen] = '/';
			pathlen++;
			fullpath[pathlen] = '\0';
		}

		DIR* dir = opendir(fullpath);
		if (!dir) {
			err_fatal(fullpath);
		}

		struct dirent* entry;
		errno = 0;
		while ((entry = readdir(dir))) {
			if (!opt_hidden() && entry->d_name[0] == '.') {
				continue;
			}

			strcpy(fullpath + pathlen, entry->d_name);
			if (opt_follow()) {
				ret = stat(fullpath, &sb);
			}
			else {
				ret = lstat(fullpath, &sb);
			}
			if (ret == -1) {
				err_fatal(fullpath);
			}

			show_entry(fullpath, entry->d_name, &sb);
			fullpath[pathlen] = '\0';
		}
		if (errno != 0) {
			err_fatal("readdir");
		}

		if (!opt_long()) {
			printf("\n");
		}

		if (opt_recursive()) {
			printf("\n");
			rewinddir(dir);

			while ((entry = readdir(dir))) {
				if (!opt_hidden() && entry->d_name[0] == '.') {
					continue;
				}

				strcpy(fullpath + pathlen, entry->d_name);
				if (opt_follow()) {
					ret = stat(fullpath, &sb);
				}
				else {
					ret = lstat(fullpath, &sb);
				}
				if (ret == -1) {
					err_fatal(fullpath);
				}

				if (S_ISDIR(sb.st_mode)) {
					myls(fullpath);
				}
				
				fullpath[pathlen] = '\0';
			}
			if (errno != 0) {
				err_fatal("readdir");
			}
		}

		closedir(dir);
	}
	else {
		show_entry(fullpath, fullpath, &sb);
		if (!opt_long()) {
			printf("\n");
		}
	}
}

int main(int argc, char* argv[]) {
	int n_opts = 0, pathlen = 0, ret;
	struct stat sb;
	const char* path = NULL;

	// 解析命令行参数
	for (int i = 1; i < argc; i++) {
		if (argv[i][0] == '-') {
			if (OPTIONS_SIZE < n_opts + strlen(argv[i]) -1) {
				fprintf(stderr, "%s\n", "too much options");
				exit(-1);
			}

			strcpy(options + n_opts, argv[i] + 1);
			n_opts += strlen(argv[i]) - 1;
		}
		else if (path) {
			fprintf(stderr, "%s\n", "multiple targets are provided");
			exit(-1);
		}
		else {
			path = argv[i];
		}
	}

	if (!path) {
		path = ".";
	}

    ret = mkdir(target_dir, 0755);
    if (ret == -1 && errno != EEXIST) {
        err_fatal("mkdir");
    }

    myls(path);
	
	return 0;
}
